package com.yiyun.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yiyun.blog.dao.dos.Archives;
import com.yiyun.blog.dao.mapper.ArticleBodyMapper;
import com.yiyun.blog.dao.mapper.ArticleMapper;
import com.yiyun.blog.dao.mapper.ArticleTagMapper;
import com.yiyun.blog.dao.pojo.Article;
import com.yiyun.blog.dao.pojo.ArticleBody;
import com.yiyun.blog.dao.pojo.ArticleTag;
import com.yiyun.blog.dao.pojo.SysUser;
import com.yiyun.blog.service.*;
import com.yiyun.blog.utils.UserThreadLocal;
import com.yiyun.blog.vo.*;
import com.yiyun.blog.vo.params.ArticleParam;
import com.yiyun.blog.vo.params.PageParams;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author Bao
 * @create 2022-01-01-19:29
 */
@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private TagService tagService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private ArticleTagMapper articleTagMapper;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    // 文章列表
//    @Override
//    public Result listArticle(PageParams pageParams) {
//
//        /**
//         * 1、分页查询 article数据库表
//         *
//         * */
//        Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
//        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//        // 添加分类查询条件
//        if (pageParams.getCategoryId() != null){
//            // and category_id = #{categoryId}
//            queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
//        }
//        // 添加标签查询条件
//        List<Long> articleIdList = new ArrayList<>();
//        if (pageParams.getTagId() != null){
//            // 加入标签 条件查询
//            LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
//            articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
//            List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper);
//            for (ArticleTag articleTag : articleTags) {
//                articleIdList.add(articleTag.getArticleId());
//            }
//            if (articleIdList.size() > 0){
//                // and id in (1,2,3)
//                queryWrapper.in(Article::getId,articleIdList);
//            }
//        }
//        //是否置顶进行排序
//        //order by create_date desc 按照weight进行降序排序
//        queryWrapper.orderByDesc(Article::getWeight);
//
//
//        //page: 分页查询条件 queryWrapper: 查询数据库的条件;
//        //筛选出所有符合条件的数据，再对筛选过后的数据按规则进行分页输出
//        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
//
//        //查询数据库得到的信息：分页查询
//        List<Article> records = articlePage.getRecords();
//
//        //list可以直接返回吗？很明显不能，数据库取得信息并不是全部需要，还需要数据库中没有的(vo对象)
//
//        List<ArticleVo> articleVoList = copyList(records,true,true);
//        return Result.success(articleVoList);
//    }

    // 上面功能实现 是用mybatis-plus实现，下面是把功能改成了xml配置文件实现，还新添加了按日期查询
    @Override
    public Result listArticle(PageParams pageParams){
        Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());

        IPage<Article> articleIPage = articleMapper.listArticle(page,
                pageParams.getCategoryId(),
                pageParams.getTagId(),
                pageParams.getYear(),
                pageParams.getMonth());
        List<Article> records = articleIPage.getRecords();
        List<ArticleVo> recordsVo = copyList(records,true,true);
        return Result.success(recordsVo);
    }

    //最热文章
    @Override
    public Result hotArticle(int limit) {

        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.select(Article::getId,Article::getTitle);
        queryWrapper.last("limit "+limit);
        //select id,title from article order by view_counts desc limit 3
        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(copyList(articles,false,false));
    }

    //最新文章
    @Override
    public Result newArticles(int limit) {

        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getCreateDate);
        queryWrapper.select(Article::getId,Article::getTitle);
        queryWrapper.last("limit "+limit);
        //select id,title from article order by create_data desc limit 3
        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(articles);
    }

    //文章归档
    @Override
    public Result listArchives() {

        List<Archives> archivesList =  articleMapper.listArchives();

        return Result.success(archivesList);
    }

    @Autowired
    private ThreadService threadService;

    //查找文章
    @Override
    public Result findArticleById(Long articleId) {

        /*
        * 1、根据id查询 文章信息
        * 2、根据bodyId和categoryId 去关联查询
        * */
        Article article = articleMapper.selectById(articleId);
        ArticleVo articleVo = copy(article,true,true,true,true);

        //查看完文章，新增阅读数
        //查看完文章之后，本应该直接返回数据，这时候做了一个更新操作，更新时加写锁，阻塞其他的读操作，性能就会比较低
        //更新 增加了此次接口的耗时 如果更新出问题，不能影响 查看文章的操作
        // 解决的方式使用线程池 把更新操作扔到线程池中去执行，就和主线程不相关了
        threadService.updateArticleViewCount(articleMapper, article);
        return Result.success(articleVo);
    }

    // 发布文章
    @Override
    public Result publishArticle(ArticleParam articleParam) {
        // 能通过UserThreadLocal获取用户信息，此接口要加入到登录拦截当中
        SysUser sysUser = UserThreadLocal.get();
        /**
         * 1、发布文章 目的 构建Article对象
         * 2、作者id 当前登录的用户
         * 3、标签 将标签加入到 关联列表当中
         * 4、body 内容存储 article bodyId
         * */
        Article article = new Article();
        boolean isEdit = false;
        if (articleParam.getId() != null){
            // 说明是编辑文章的 发布
            article = new Article();
            article.setId(articleParam.getId());
            article.setTitle(articleParam.getTitle());
            article.setSummary(articleParam.getSummary());
            article.setCategoryId(Long.parseLong(articleParam.getCategory().getId()));
            articleMapper.updateById(article);
            isEdit = true;
        }else {
            article = new Article();
            article.setAuthorId(sysUser.getId());
            article.setWeight(Article.Article_Common);
            article.setViewCounts(0);
            article.setTitle(articleParam.getTitle());
            article.setSummary(articleParam.getSummary());
            article.setCommentCounts(0);
            article.setCreateDate(System.currentTimeMillis());
            article.setCategoryId(Long.parseLong(articleParam.getCategory().getId()));
            // 插入之后，会生成一个文章id
            articleMapper.insert(article);
        }
        // tag
        List<TagVo> tags = articleParam.getTags();
        if(tags != null){
            for (TagVo tag : tags) {
                // mp的insert操作后id值会写回到参数对象中
                Long articleId = article.getId();
                if (isEdit){
                    //  先删除
                    LambdaQueryWrapper<ArticleTag> queryWrapper  = Wrappers.lambdaQuery();
                    queryWrapper.eq(ArticleTag::getArticleId,articleId);
                    articleTagMapper.delete(queryWrapper);
                    isEdit = false;
                }
                ArticleTag articleTag = new ArticleTag();
                articleTag.setTagId(Long.parseLong(tag.getId()));
                articleTag.setArticleId(articleId);
                articleTagMapper.insert(articleTag);
            }
            isEdit = true;
        }
        // body
        if (isEdit){
            // 先删除
            LambdaQueryWrapper<ArticleBody> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(ArticleBody::getArticleId,article.getId());
            articleBodyMapper.delete(queryWrapper);
        }
        ArticleBody articleBody = new ArticleBody();
        articleBody.setArticleId(article.getId());
        articleBody.setContent(articleParam.getBody().getContent());
        articleBody.setContentHtml(articleParam.getBody().getContentHtml());
        articleBodyMapper.insert(articleBody);

        article.setBodyId(articleBody.getId());
        articleMapper.updateById(article);
        Map<String,String> map = new HashMap<>();
        // toString 也能防止精度损失问题
        map.put("id",article.getId().toString());

        if (isEdit){
            // 发送一条消息 当前文章更新了，更新一下缓存吧
            ArticleMessage articleMessage = new ArticleMessage();
            articleMessage.setArticleId(article.getId());
            // 发送消息 第一个参数：消息队列的名字  第二个参数：消息的内容
//            rocketMQTemplate.convertAndSend("blog-update-article",articleMessage);
        }

        return Result.success(map);
    }

    // 搜索文章
    @Override
    public Result searchArticle(ArticleParam articleParam) {
        List<SearchArticleVo> searchArticleVos = new ArrayList<>();
        String search = articleParam.getSearch();
        if(StringUtils.isBlank(search)){
            log.info("search的值为空");
            LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper();
            queryWrapper.orderByDesc(Article::getViewCounts);
            queryWrapper.select(Article::getId,Article::getTitle);
            List<Article> articles = articleMapper.selectList(queryWrapper);
            for (Article article : articles) {
                SearchArticleVo searchArticleVo = new SearchArticleVo();
                searchArticleVo.setId(String.valueOf(article.getId()));
                searchArticleVo.setTitle(article.getTitle());
                searchArticleVos.add(searchArticleVo);
            }
        }else{
            log.info("search的值不为空");
            LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper();
            queryWrapper.orderByDesc(Article::getViewCounts);
            queryWrapper.select(Article::getId,Article::getTitle);
            queryWrapper.like(Article::getTitle,search);
            List<Article> articles = articleMapper.selectList(queryWrapper);
            for (Article article : articles) {
                SearchArticleVo searchArticleVo = new SearchArticleVo();
                searchArticleVo.setId(String.valueOf(article.getId()));
                searchArticleVo.setTitle(article.getTitle());
                searchArticleVos.add(searchArticleVo);
            }
        }
        return Result.success(searchArticleVos);
    }

    private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {

        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record:records){
            articleVoList.add(copy(record,isTag,isAuthor,false,false));
        }
        return articleVoList;
    }

    //
    private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory) {

        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article record:records){
            articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory));
        }
        return articleVoList;
    }

    @Autowired
    private CategoryService categoryService;

    private ArticleVo copy(Article article,boolean isTag,boolean isAuthor,boolean isBody,boolean isCategory){

        ArticleVo articleVo = new ArticleVo();
        // article将相同的属性的值，赋给articleVo
        BeanUtils.copyProperties(article,articleVo);
        articleVo.setId(String.valueOf(article.getId()));

        // 由于ArticleVo中createDate的类型 和 Article中的不一样，上面的方法不会把值赋过来
        // 所以需要手动进行转换
        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));

        //不是所有的接口，都需要标签，作者信息
        if(isTag){
            Long articleId = article.getId();

            articleVo.setTags(tagService.findTagsByArticleId(articleId));
        }
        if (isAuthor){
            Long authorId = article.getAuthorId();
            UserVo author = new UserVo();
            author.setId(String.valueOf(authorId));
            author.setNickname(sysUserService.findUserById(authorId).getNickname());
            articleVo.setAuthor(author);
        }
        if(isBody){
            Long bodyId = article.getBodyId();
            articleVo.setBody(findArticleBodyById(bodyId));
        }
        if(isCategory){
            Long categoryId = article.getCategoryId();
            articleVo.setCategory(categoryService.findCategoryById(categoryId));
        }
        return articleVo;
    }

    @Autowired
    private ArticleBodyMapper articleBodyMapper;
    // 文章详情
    private ArticleBodyVo findArticleBodyById(Long bodyId) {

        ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
        ArticleBodyVo articleBodyVo = new ArticleBodyVo();
        articleBodyVo.setContent(articleBody.getContent());

        return articleBodyVo;
    }
}


/**
 * BeanUtils.copyProperties(a, b);
 *
 * b中的存在的属性，a中一定要有，但是a中可以有多余的属性；
 * a中与b中相同的属性都会被替换，不管是否有值；
 * a、 b中的属性要名字相同，才能被赋值，不然的话需要手动赋值；
 * */